process.env.TZ = 'Asia/Shanghai';

fs = require('fs');
// mysql = require('mysql');

https = require('https');

var uim = require('./uim.js');

constains = require('constants');

var qs = require('querystring');
const urlLib = require('url');//处理url相关

// var exec_request = function (req, res) {
//     res.writeHead(200, { 'Content-Type': 'application/json' });
//     res.end('{"status":"ERROR"}');
// };

// 环境配置（以下划线开头的配置表示允许在运行中重新赋值并立即生效）
const ENV = {
    SOCKET_ROUTER_ID: fs.readFileSync('./.env/SOCKET_ROUTER_ID', 'utf-8').trim(), // 路由服务器编号
    SOCKET_SERVER_ID: fs.readFileSync('./.env/SOCKET_SERVER_ID', 'utf-8').trim(), // 服务器编号（与router-server中server_list数组对应）
    SOCKET_SERVER_HOST: fs.readFileSync('./.env/SOCKET_SERVER_HOST', 'utf-8').trim(),
    LISTEN_PORT: fs.readFileSync('./.env/LISTEN_PORT', 'utf-8').trim(),
    REDIS: {
        PORT: fs.readFileSync('./.env/redis.port', 'utf-8').trim(),
        HOST: fs.readFileSync('./.env/redis.host', 'utf-8').trim(),
        PASSWORD: fs.readFileSync('./.env/redis.password', 'utf-8').trim(),
    },
    HTTPS: {
        key: fs.readFileSync('./socket-server.key'),
        cert: fs.readFileSync('./socket-server.pem'),
        // secureOptions: constains.SSL_OP_NO_TLSv1_2 | constains.SSL_OP_NO_TLSv1_1,
    },
    _DEBUG_MODE: true,
    _UPDATE_SOCKET_SERVER_LIMIT: 1500, // 每隔1.5秒活跃一次
    _SESSION_EXPIRE_LIMIT: 86400000, // 缓存一天内的会话
    _CLIENT_API_THREAD_MAX: 50000, // 单个客户端的最大接口并发
    _SERVER_API_THREAD_MAX: 100000, // 整个服务器（进程）的最大接口并发
}

console.log(ENV);

var server_api_thread_working = 0; // 整个服务器（进程）工作中的接口总数
var api_req_index = 0n;
var api_req_working = {}; // 工作中接口回调

var redis = require('redis');
const { send } = require('process');
const { SSL_OP_SSLEAY_080_CLIENT_DH_BUG } = require('constants');

// 用于连接router本机redis，用于与socket-server保持通信，接收update-server
var console_redis_sub = undefined;

// 用于连接router本机redis，用于与socket-server保持通信，向update-server发出广播事件
var console_redis_pub = redis.createClient(ENV.REDIS.PORT, ENV.REDIS.HOST, { auth_pass: ENV.REDIS.PASSWORD });
console_redis_pub.on('ready', function (err) {
    // 如果连接控制台失败
    if (err) {
        throw 'pub连接本地控制台失败'
    } else {
        console.log('pub已连接到本地控制台');
        init_porject_list(); // 初始项目列表
    }
});
var init_redis_subscribe = function () {
    console_redis_sub = redis.createClient(ENV.REDIS.PORT, ENV.REDIS.HOST, { auth_pass: ENV.REDIS.PASSWORD });
    console_redis_sub.on('ready', function (err) {
        // 如果连接控制台失败
        if (err) {
            throw 'sub连接本地控制台失败'
        } else {
            console.log('sub已连接到本地控制台');
        }
    });
    console_redis_sub.subscribe('socket_server_' + ENV.SOCKET_SERVER_ID); // 订阅本server的广播信息
    console_redis_sub.on('subscribe', function (channel, count) {
        console.log(`got subscribe event: ${channel} and count is ${count}`);
    });
    console_redis_sub.on('connect', function () {
        //订阅客户端接收到消息
        console_redis_sub.on('message', function (channel, message) {
            var time = new Date().getTime();
            var response = `${time} received message from ${channel}:${message}`;
            // console.log(response);
            switch (channel) {
                case 'emit_event':
                    emit_event(message, time);
                case 'debug':
                    run_debug(message);
                default:
                    break;
            }
        });
        ENV._STATE = 1; // socket-server已就绪
        console.log('socket-server已就绪');
        update_socket_server();

    });
}

// 用于将接口请求写入队列
var project_redis_handle_pub = {
    // '1_1': null,
};
// 用于订阅项目接口执行回调结果
var project_redis_handle_sub = {
    // '1_1': null,
};

var default_project_obj = {
    // project_id: 0, // 项目编号
    // project_code: '',
    // project_name: '',
    // enable: true, // 项目启用状态
    redis_return_err: 0, // redis连接返回错误数量
    // redis_return_ready: 0, // redis连接返回成功数量
    redis_connected: [], // 连接中的redis_id数组
    redis_return: 0, // redis连接返回总数
    redis_list: [
        // {
        //     redis_id: 0, // 队列编号
        //     ready: false,
        //     port: '', // 端口
        //     host: '', // 地址
        //     password: '', // 密码
        // }
    ],
};
// 关联当前router的项目列表
var project_list = [];
var init_porject_list = function () {
    // 从redis/mysql中读取列表
    // 遍历并初始redis列表
    console_redis_pub.get('console_deploy|project_list|socket_router_' + ENV.SOCKET_ROUTER_ID, function (err, result) {
        if (err) {
            throw '读取项目列表失败';
        }
        var tmp_project_list = uim.parseJSON(result || '[]');
        if (!tmp_project_list) {
            throw '解析项目列表时数据异常';
        }
        for (var tmp_i in tmp_project_list) {
            // 同时使用project_id和project_code两个索引
            project_list[tmp_project_list[tmp_i].project_id] = Object.assign(tmp_project_list[tmp_i], JSON.parse(JSON.stringify(default_project_obj)));
            project_list[tmp_project_list[tmp_i].project_code] = tmp_project_list;
            get_project_redis_list(tmp_project_list[tmp_i]);
        }
        console.log('初始项目列表成功：');
        console.log(tmp_project_list);
        init_app_list(); // 初始应用列表
    });
}

// 该路由所服务的应用列表 
var app_list = {
    // '1': {
    //     app_id: 0,
    //     app_name: '',
    //     enable: true, // 应用启用状态
    //     project_id: 1, // 所属项目
    // }
};
var init_app_list = function () {
    console_redis_pub.get('console_deploy|app_list|socket_router_' + ENV.SOCKET_ROUTER_ID, function (err, result) {
        if (err) {
            return console.log('读取应用列表失败');
        }
        var tmp_app_list = uim.parseJSON(result);
        if (!tmp_app_list) {
            return console.log('解析应用列表时数据异常');
        }
        for (var tmp_i in tmp_app_list) {
            app_list[tmp_app_list[tmp_i].app_id] = tmp_app_list[tmp_i];
        }
        console.log('初始应用列表成功：');
        console.log(app_list);
        init_redis_subscribe()
    });
}
var get_project_redis_list = function (obj_project) {
    console_redis_pub.get('console_deploy|redis_list|socket_router_' + ENV.SOCKET_ROUTER_ID + '|project_' + obj_project.project_id, function (err, result) {
        if (err) {
            return console.log('读取项目redis列表失败');
        }
        var tmp_redis_list = uim.parseJSON(result);
        if (!tmp_redis_list) {
            return console.log('解析项目“' + obj_project.project_name + '”redis列表时数据异常');
        }
        obj_project.redis_list = tmp_redis_list;
        for (var tmp_i in obj_project.redis_list) {
            obj_project.redis_list[tmp_i].ready = false;
            connect_project_redis_pub(obj_project, obj_project.redis_list[tmp_i]);
        }
    });
}

var connect_project_redis_pub = function (obj_project, obj_redis) {

    var redis_handle = obj_project.project_id + '_' + obj_redis.redis_id;

    project_redis_handle_pub[redis_handle] = redis.createClient(obj_redis.port, obj_redis.host, { auth_pass: obj_redis.password });
    project_redis_handle_pub[redis_handle].on('ready', function (err) {
        if (err) {
            console.log(err);
            console.log('项目“' + obj_project.project_name + '”连接接口队列失败（pub）：' + redis_handle);
            obj_project.redis_return_err++;
        } else {
            obj_redis.ready = true;
            console.log('项目“' + obj_project.project_name + '”已连接到接口队列（pub）：' + redis_handle);
            connect_project_redis_sub(obj_project, obj_redis);
        }
        obj_project.redis_return++;

        // todo 以下if块作为回调函数传入，则本函数既可在启动时初始，也可在运行中控制新连接
        // 当所有redis连接返回
        if (obj_project.redis_return == obj_project.redis_list.length) {
            // 如果有失败的redis连接
            if (obj_project.redis_return_err) {
                console.log('项目“' + obj_project.project_name + '”共尝试连接队列' + obj_project.redis_return +
                    '个，有' + obj_project.redis_return_err + '个连接失败，请解决后通过控制台重新发起连接！');
            }
        }
    });
}

var connect_project_redis_sub = function (obj_project, obj_redis) {

    var redis_handle = obj_project.project_id + '_' + obj_redis.redis_id;

    project_redis_handle_sub[redis_handle] = redis.createClient(obj_redis.port, obj_redis.host, { auth_pass: obj_redis.password });
    project_redis_handle_sub[redis_handle].on('ready', function (err) {
        if (err) {
            console.log('项目“' + obj_project.project_name + '”连接接口队列失败（sub）：' + redis_handle);
            // var tmp_redis_index = project_list[obj_project].redis_connected.indexOf(obj_redis.redis_id);
            // if (tmp_redis_index > -1) {
            //     project_list[obj_project].redis_connected.splice(tmp_redis_index, 1);
            // }
        } else {
            console.log('项目“' + obj_project.project_name + '”已连接到接口队列（sub）：' + redis_handle);
            obj_project.redis_connected.push(obj_redis.redis_id);
        }
    });
    project_redis_handle_sub[redis_handle].subscribe('api_producer|socket_server_' + ENV.SOCKET_SERVER_ID + '|project_' + obj_project.project_id); // 订阅本server发起的接口执行回调
    project_redis_handle_sub[redis_handle].on('subscribe', function (channel, count) {
        console.log(`got subscribe event: ${channel} and count is ${count}`);
    });
    project_redis_handle_sub[redis_handle].on('connect', function () {
        //订阅客户端接收到消息
        project_redis_handle_sub[redis_handle].on('message', function (channel, message) {
            var time = new Date().getTime();
            var response = `${time} received message from ${channel}:${message}`;
            console.log(response);
            var api_result = uim.parseJSON(message);
            if (api_result && api_req_working[api_result.req_id]) {
                var req_result = api_req_working[api_result.req_id];
                delete api_req_working[api_result.req_id];
                req_result.call_back(Object.assign({
                    req_id: api_result.req_id,
                    duration: api_result.duration,
                    server_id: api_result.server_id,
                    api_time_ms: api_result.api_time_ms,
                    return_ms: api_result.return_ms,
                }, api_result.data));
            }

        });
    });

}

var online_client_number = 0; // 在线总人数
var update_socket_server = function () {
    // console.log('update_socket_server');
    console_redis_pub.publish('update_socket_server', ENV.SOCKET_SERVER_ID + ',' + online_client_number, function (err, repay) {
        setTimeout(() => {
            update_socket_server();
        }, ENV._UPDATE_SOCKET_SERVER_LIMIT);
    });
}

var get_session = function (session_id, callback) {
    console_redis_pub.get('session_' + session_id, function (result) {

        var account_id = result;
        callback(account_id);
    });
}
var api_redis_index = 0;
var add_api_req = function (req, call_back) {
    // req = {
    //     code: '',
    //     data: {},
    //     app_id: '',
    //     client_id: '',
    //     session_id: '',
    //     ip: ''
    // };

    if (server_api_thread_working >= ENV._SERVER_API_THREAD_MAX) {
        call_back({
            code: 19,
            msg: '触发限流：服务器繁忙',
            description: 'todo 记录日志'
        });
        return console.log('触发限流：服务器繁忙');
    }
    var req_time_ms = new Date().getTime();
    server_api_thread_working++;
    api_req_index++;


    // 定义变量：socket服务器发送前的请求对象
    var send_req = {
        code: req.code,
        args: JSON.stringify(req.data),
        app_id: req.app_id,
        ip: req.ip,
        client_id: req.client_id,
        session_id: req.session_id,
        // api_id: 0,
        socket_server_id: ENV.SOCKET_SERVER_ID,
        run_timeout: 0,
        project_id: app_list[req.app_id].project_id,
        req_id: api_req_index.toString(),
    };

    var lpush_data = JSON.stringify(send_req);
    // 加入redis队列
    api_redis_index = (api_redis_index + 1) % project_list[send_req.project_id].redis_connected.length;
    var redis_handle = send_req.project_id + '_' + project_list[send_req.project_id].redis_connected[api_redis_index];
    console.log({ api_redis_index, redis_connected: project_list[send_req.project_id].redis_connected });
    project_redis_handle_pub[redis_handle].lpush('api_queue|project_' + send_req.project_id, lpush_data, function (err, reply) {
        if (err) {
            call_back({
                code: 19,
                msg: '加入队列失败',
                description: 'todo 记录日志'
            });
            // 记录到本地数组，写入文件日志或mysql日志
        } else {
            api_req_working[send_req.req_id] = {
                call_back: function (api_result) {
                    delete api_req_working[api_result.req_id];
                    server_api_thread_working--;
                    call_back(api_result);
                },
                send_req,
                // event_enable: api_info.event_enable,
                req_time_ms,
            }
        }
    });
}
function getClientIp(req) {
    return req.headers['x-forwarded-for'] ||
        req.connection.remoteAddress ||
        req.socket.remoteAddress ||
        req.connection.socket.remoteAddress;
};
// var app = https.createServer(ENV.HTTPS, exec_request).listen(ENV.LISTEN_PORT);
var app = https.createServer(ENV.HTTPS, function (req, res) {
    res.writeHead(200, {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*'
    });
    //GET
    var obj = urlLib.parse(req.url, true);
    var url = obj.pathname;
    const GET = obj.query;

    //POST
    var str = '';
    var POST = '';
    req.on('data', function (data) {
        str += data;
    });
    req.on('end', function () {
        POST = qs.parse(str);
        console.log({ req_url: req.url, url, GET, POST });

        // var url = req.url.split('?')[0];
        switch (url) {
            case '/api':

                if (!POST.init_id || !POST.deploy_app_id) {
                    // todo 直接加ip黑名单并断开连接
                    res.end(JSON.stringify({
                        code: 19,
                        msg: '非法操作：未初始化不允许调取接口',
                        description: 'todo 记录日志'
                    }));
                    return console.log('非法操作：未初始化不允许调取接口');
                }
                // // 校验token
                // var verify_token = uim.verifySafeString(token, uim.config.enum.safeStringType.SOCKET_ROUTER_TOKEN);
                // if (!verify_token || verify_token.id != ENV.SOCKET_SERVER_ID) {
                //     return socket.emit('fail', { verify_token });
                // }
                // 校验init_id
                var verify_init_id = uim.verifySafeString(POST.init_id, uim.config.enum.safeStringType.CLIENT_ID);
                if (!verify_init_id) {
                    res.end(JSON.stringify({
                        code: 19,
                        msg: '非法操作：init_id检验失败',
                        description: 'todo 记录日志'
                    }));
                    return console.log('非法操作：init_id检验失败');
                }
                // 校验app_id
                var verify_app_id = uim.verifySafeString(POST.deploy_app_id, uim.config.enum.safeStringType.DEPLOY_APP_ID);
                if (!verify_app_id) {
                    res.end(JSON.stringify({
                        code: 19,
                        msg: '非法操作：deploy_app_id检验失败',
                        description: 'todo 记录日志'
                    }));
                    return console.log('非法操作：deploy_app_id检验失败');
                }
                if (!app_list[verify_app_id.id]) {
                    res.end(JSON.stringify({
                        code: 19,
                        msg: '应用不存在或未部署',
                        description: 'todo 记录日志'
                    }));
                    return console.log('应用不存在或未部署');
                }
                if (!app_list[verify_app_id.id].enable) {
                    res.end(JSON.stringify({
                        code: 19,
                        msg: '应用未启用',
                        description: 'todo 记录日志'
                    }));
                    return console.log('应用未启用');
                }
                var api_req = {
                    code: POST.code,
                    data: uim.parseJSON(POST.data),
                    session_id: POST.init_id, // 字符串作会话
                    client_id: verify_init_id.id, // 数值作标识
                    app_id: verify_app_id.id,
                    ip: getClientIp(req)
                };
                add_api_req(api_req, function (api_result) {
                    res.end(JSON.stringify(api_result));
                });
                break;

            default:
                res.end('{"status":"ERROR"}');
                break;
        }
    });
}).listen(ENV.LISTEN_PORT);
var io = require('socket.io').listen(app);
io.sockets.on('connection', function (socket) {
    online_client_number++;
    console.log(uim.dateTime() + ' - 客户端连接：' + socket.request.connection.remoteAddress + '。在线总人数：' + online_client_number + '。socket.id：' + socket.id);
    //验证socket
    socket.on('check', function ({ token, deploy_app_id, init_id, session_id }) {
        // 校验token
        var verify_token = uim.verifySafeString(token, uim.config.enum.safeStringType.SOCKET_ROUTER_TOKEN);
        if (!verify_token || verify_token.id != ENV.SOCKET_SERVER_ID) {
            return socket.emit('fail', { verify_token });
        }
        // 校验init_id
        var verify_init_id = uim.verifySafeString(init_id, uim.config.enum.safeStringType.CLIENT_ID);
        if (!verify_init_id) {
            // todo 如果再判断时间戳（需要router与server时间同步），是否不需要在上面验证token了
            return socket.emit('fail', { verify_init_id });
        }
        socket.init_id = verify_init_id.id;
        socket.client_id = init_id;
        // 校验app_id
        var verify_app_id = uim.verifySafeString(deploy_app_id, uim.config.enum.safeStringType.DEPLOY_APP_ID);
        if (!verify_app_id) {
            return socket.emit('fail', { verify_app_id });
        }
        if (!app_list[verify_app_id.id]) {
            return socket.emit('fail', { message: '应用不存在或未部署' });
        }
        if (!app_list[verify_app_id.id].enable) {
            return socket.emit('fail', { message: '应用未启用' });
        }
        socket.app_id = verify_app_id.id;
        socket.project_id = app_list[verify_app_id.id].project_id;

        // todo 是否也可以直接在init_id中检查session？（继续由客户端传入session，可减去无会话时的redis查询操作）
        if (session_id) {
            // 校验session_id
            var verify_session_id = uim.verifySafeString(session_id, uim.config.enum.safeStringType.SESSION_ID);
            if (!verify_session_id) {
                return socket.emit('fail', { verify_session_id });
            }
            var now_time = new Date().getTime();
            if (now_time - verify_session_id.time > ENV.SESSION_EXPIRE_LIMIT) {
                // 已经是冷数据
                socket.emit('ok');
            } else {
                // 仍是热数据，从缓存中读取
                get_session(verify_session_id.id, function (account_id) {
                    socket.emit('ok', account_id);
                });
            }
        } else {
            // 未登录
            socket.emit('ok');
        }

        socket.api_thread_working = 0; // 执行中接口数量
        socket.api_thread_max = ENV._CLIENT_API_THREAD_MAX; // 最大并发接口数量
    });

    //请求接口
    socket.on('api', function (req, call_back) {
        if (!socket.init_id) {
            // todo 直接加ip黑名单并断开连接
            call_back({
                code: 19,
                msg: '非法操作：未初始化不允许调取接口',
                description: 'todo 记录日志'
            });
            // socket.disconnect();
            return console.log('非法操作：未初始化不允许调取接口');
        }
        // call_back 返回number类型值为req_id，表示该接口已经受理，否则返回object类型，表示不受理该接口的错误回调

        if (socket.api_thread_working >= socket.api_thread_max) {
            call_back({
                code: 19,
                msg: '非法操作：此客户端接口线程已达最大',
                description: 'todo 记录日志'
            });
            // socket.disconnect();
            return console.log('非法操作：此客户端接口线程已达最大');
        }
        Object.assign(req, {
            session_id: socket.client_id, // 字符串作会话
            client_id: socket.init_id, // 数值作标识
            app_id: socket.app_id,
            ip: socket.request.connection.remoteAddress,
        });
        socket.api_thread_working++;
        add_api_req(req, function (api_result) {
            socket.api_thread_working--;
            call_back(api_result);
        });
    });

    //调试
    socket.on('debug', function (debug_code) {
        console.log('debug: ' + debug_code);
        //此调试方法只在调试模式下使用
        if (ENV._DEBUG_MODE) eval(debug_code);
    });

    socket.on('disconnect', function (data) {
        online_client_number--;
        console.log(uim.dateTime() + ' - 客户端断开：' + socket.request.connection.remoteAddress + '。在线总人数：' + online_client_number + '。socket.id：' + socket.id);
    });

    // 触发客户端验证事件
    socket.emit('check');

});
var run_debug = function (debug_code) {
    console.log('debug: ' + debug_code);
    //此调试方法只在调试模式下使用
    if (ENV._DEBUG_MODE) eval(debug_code);
}


console.log('router server v2.0 is running: ' + uim.dateTime());